package com.totem.base.util;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Method;
import com.totem.base.constants.CommonConstants;
import com.totem.base.filter.support.CachedRequestWrapper;
import com.totem.base.filter.support.ModifyRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

@Slf4j
public class FilterUtil {
    private static final List<String> DEFAULT_DOWNLOAD_CONTENT_TYPE = List.of(
            "application/vnd.ms-excel",//.xls
            "application/msexcel",//.xls
            "application/cvs",//.cvs
            MediaType.APPLICATION_OCTET_STREAM_VALUE,//.*（ 二进制流，不知道下载文件类型）
            "application/x-xls",//.xls
            "application/msword",//.doc
            MediaType.TEXT_PLAIN_VALUE,//.txt
            "application/x-gzip"//.gz
    );

    public static final Set<String> IGNORE_DOC_URLS = Set.of(
            "/",
            "/swagger-ui.html",
            "/swagger-ui/",
            "/doc.html",
            "/favicon.ico",
            "/**/*.html",
            "/**/*.css",
            "/**/*.js",
            "/swagger-resources/**",
            "/v3/api-docs/**"
    );

    private FilterUtil(){}

    public static String getTraceId(final HttpServletRequest request){
        String traceId = request.getHeader(CommonConstants.TRACE_ID);
        return traceId;
    }

    public static String getHeadersForLog(final HttpServletRequest request){
        String origin = "Origin:"+request.getHeader("Origin");
        String userAgent = "User-Agent:"+request.getHeader("User-Agent");
        String referer = "Referer:"+request.getHeader("Referer");
        final List<String> headers = List.of(origin, userAgent, referer);
        return '[' + String.join(",", headers) + ']';
    }

    public static String getRemoteAddress(final HttpServletRequest request) {
        Function<String, String> extractIp = name -> request.getHeader(name);
        Supplier<String> finalExtractIp = ()->request.getRemoteAddr();
        return resolveIp(extractIp, finalExtractIp);
    }

    public static String getRemoteAddress(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        Map<String, String> headers = request.getHeaders().toSingleValueMap();
        Function<String, String> extractIp = name -> headers.get(name);
        Supplier<String> finalExtractIp = ()->request.getRemoteAddress().getAddress().getHostAddress();
        return resolveIp(extractIp, finalExtractIp);
    }

    public static String resolveIp(Function<String, String> getIp, Supplier<String> finalGetIp){
        String ip = "";
        String unknown = "unknown";
        Predicate<String> checkIp = str -> StringUtils.isBlank(str)||unknown.equalsIgnoreCase(str);
        if (checkIp.test(ip)) {
            ip = getIp.apply("X-Forwarded-For");
        }
        if (checkIp.test(ip)) {
            ip = getIp.apply("Proxy-Client-IP");
        }
        if (checkIp.test(ip)) {
            ip = getIp.apply("WL-Proxy-Client-IP");
        }
        if (checkIp.test(ip)) {
            ip = getIp.apply("HTTP_CLIENT_IP");
        }
        if (checkIp.test(ip)) {
            ip = getIp.apply("HTTP_X_FORWARDED_FOR");
        }
        if (checkIp.test(ip)) {
            ip = getIp.apply("X-Real-IP");
        }
        if (checkIp.test(ip)) {
            ip = finalGetIp.get();
        }
        //对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 0) {
            ip = Arrays.stream(ip.split(",")).findFirst().orElse("");
        }
        if ("0:0:0:0:0:0:0:1".equals(ip)) {
            ip = "127.0.0.1";
        }
        return ip;
    }

    public static String getRequestHeaders(final HttpServletRequest request) {
        final Enumeration<String> headerNames = request.getHeaderNames();
        final List<String> headers = new ArrayList<>();
        while (headerNames.hasMoreElements()) {
            final String key = headerNames.nextElement();
            headers.add(key + ':' + request.getHeader(key));
        }
        return '[' + String.join(",", headers) + ']';
    }

    public static String getResponseHeaders(final HttpServletResponse response) {
        final Collection<String> headerNames = response.getHeaderNames();
        final List<String> headers = new ArrayList<>();
        for(String name : headerNames){
            String key = response.getHeader(name);
            headers.add(key + ':' + response.getHeader(key));
        }
        return '[' + String.join(",", headers) + ']';
    }

    public static String getRequestParams(final ServerHttpRequest request) {
        MultiValueMap<String, String> oriParameterMap = request.getQueryParams();
        MediaType type = request.getHeaders().getContentType();
        return getRequestMap(oriParameterMap, type!=null?type.getType():"");
    }

    public static String getRequestMap(final MultiValueMap<String, String> multiValueMap, String contentType){
        Function<Set<Map.Entry<String, List<String>>>, List<String>> consumer = entrySet->{
            List<String> pairs = List.of();
            for (Map.Entry<String, List<String>> entry : entrySet) {
                final String name = entry.getKey();
                final List<String> value = entry.getValue();
                if (value == null) {
                    pairs.add(name + "=");
                } else {
                    for (final String v : value) {
                        pairs.add(name + "=" + StringUtils.trimToEmpty(v));
                    }
                }
            }
            return pairs;
        };
        return getRequestString(multiValueMap.entrySet(), contentType, consumer);
    }

    public static String getRequestParams(final HttpServletRequest request){
        final Map<String, String[]> requestParams = request.getParameterMap();
        Function<Set<Map.Entry<String, String[]>>, List<String>> consumer = entrySet->{
            List<String> pairs = new ArrayList<>();
            for (Map.Entry<String, String[]> entry : entrySet) {
                final String name = entry.getKey();
                final String[] value = entry.getValue();
                if (value == null) {
                    pairs.add(name + "=");
                } else {
                    for (final String v : value) {
                        pairs.add(name + "=" + StringUtils.trimToEmpty(v));
                    }
                }
            }
            return pairs;
        };
        return getRequestString(requestParams.entrySet(), request.getContentType(), consumer);
    }

    public static String getRequestString(final HttpServletRequest request) {
        final ContentCachingRequestWrapper cachingWrapper =  WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if(cachingWrapper != null){
            final byte[] buf = cachingWrapper.getContentAsByteArray();
            if(buf != null){
                return StrUtil.str(buf, CharsetUtil.CHARSET_UTF_8);
            }
        }
        final ModifyRequestWrapper decryptWrapper = WebUtils.getNativeRequest(request, ModifyRequestWrapper.class);
        if(decryptWrapper != null){
            final byte[] buf = decryptWrapper.getContentAsByteArray();
            if(buf != null){
                return StrUtil.str(buf, CharsetUtil.CHARSET_UTF_8);
            }
        }
        final CachedRequestWrapper cachedWrapper = WebUtils.getNativeRequest(request, CachedRequestWrapper.class);
        if (cachedWrapper != null) {
            final byte[] buf = cachedWrapper.getContentAsByteArray();
            if(buf != null){
                return StrUtil.str(buf, CharsetUtil.CHARSET_UTF_8);
            }
        }
        return StringUtils.EMPTY;
    }

    public static String getResponseString(final HttpServletResponse response) throws IOException {
        final ContentCachingResponseWrapper cachedWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (cachedWrapper != null) {
            final byte[] buf = cachedWrapper.getContentAsByteArray();
            if(buf != null){
                return StrUtil.str(buf, CharsetUtil.CHARSET_UTF_8);
            }
        }
        /*
        final CachedEncryptResponseWrapper encryptWrapper = WebUtils.getNativeResponse(response, CachedEncryptResponseWrapper.class);
        if (encryptWrapper != null) {
            final byte[] buf = encryptWrapper.getContentAsByteArray();
            if(buf != null){
                return StrUtil.str(buf, CharsetUtil.CHARSET_UTF_8);
            }
        }*/
        return StringUtils.EMPTY;
    }

    private static <T> String getRequestString(final Set<Map.Entry<String, T>> entrySet, String contentType, Function<Set<Map.Entry<String, T>>, List<String>> getPairs) {
        if(entrySet==null || entrySet.size()==0) return "";

        final List<String> pairs = getPairs.apply(entrySet);
        String requestParamsStr = CollectionUtils.isEmpty(pairs) ? StringUtils.EMPTY : String.join("&", pairs);
        if (StringUtils.equalsIgnoreCase(contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            try {
                requestParamsStr = URLDecoder.decode(requestParamsStr, StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException ignored) {
            }
        }
        return requestParamsStr;
    }

    public static boolean isUpload(final HttpServletRequest request) {
        final String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
        if (StringUtils.isBlank(contentType)) {
            return false;
        }
        return StringUtils.containsIgnoreCase(contentType, MediaType.MULTIPART_FORM_DATA_VALUE);
    }

    public static boolean isDownload(final HttpServletResponse response) {
        final String contentType = response.getContentType();
        if (StringUtils.isBlank(contentType)) {
            return false;
        }
        return DEFAULT_DOWNLOAD_CONTENT_TYPE.stream().anyMatch(it -> StringUtils.equalsIgnoreCase(it, contentType));
    }

    public static boolean ignoreMethod(final HttpServletRequest request){
        String method = request.getMethod();
        return Method.OPTIONS.name().equalsIgnoreCase(method);
    }

    public static void copyResponse(final HttpServletResponse response) {
        final ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                wrapper.copyBodyToResponse();
            } catch (IOException ignored) {
            }
        }
    }
}
